﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Shell.DomainDot
{
    public abstract class DomainInjector : IDomainInjector
    {
        private static Type mainDomainType;
        private bool disposed;

        static DomainInjector()
        {
            mainDomainType = typeof(Main<>);
        }

        public DomainInjector()
        {
            this.disposed = false;
        }

        protected abstract Type TargetDomainType
        {
            get;
        }

        protected virtual Type MainDomainType
        {
            get { return mainDomainType; }
        }

        public virtual void Inject<TDomainDot>(TDomainDot dot)
            where TDomainDot : DomainDotBase
        {
            var ps = this.GetInjectingProperties(dot);
            foreach (var p in ps)
            {
                var setMethod = p.GetSetMethod(false);
                if (setMethod != null)
                {
                    var instance = this.CreateDomainDot(dot, p);
                    if (instance != null)
                    {
                        setMethod.Invoke(dot, new object[] { instance });
                    }
                } 
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual IEnumerable<PropertyInfo> GetInjectingProperties<TDomainDot>(TDomainDot dot)
            where TDomainDot : DomainDotBase
        {
            List<PropertyInfo> ps = new List<PropertyInfo>();
            var t = dot.GetType();
            foreach (var p in t.GetProperties())
            {
                if (p.CanWrite)
                {
                    var propType = p.PropertyType;
                    if (propType.IsGenericType)
                    {
                        propType = propType.GetGenericTypeDefinition();
                    }
                    if (this.TargetDomainType == propType)
                    {
                        ps.Add(p);
                    }
                }
            }
            return ps;
        }

        protected virtual Type GetMainSegmentPocoType<TDomainDot>(TDomainDot dot)
            where TDomainDot : DomainDotBase
        {
            var p = DomainInjectHelper.GetMatchedProperty(typeof(TDomainDot), this.MainDomainType);
            return p == null ? null : p.PropertyType.GetGenericArguments()[0];
        }

        protected abstract object CreateDomainDot<TDomainDot>(TDomainDot dot, PropertyInfo domainSegment)
            where TDomainDot : DomainDotBase;

        protected virtual void OnDispose(bool disposing)
        {
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                this.OnDispose(disposing);
            }
            this.disposed = true;
        }
    }
}
